Skip to content

Support Lelo F1s V3 as Harmony protocol - #920

Open
aabbccddeeeeee wants to merge 1 commit into
buttplugio:masterfrom
aabbccddeeeeee:agent/support-lelo-f1sv3
Open

Support Lelo F1s V3 as Harmony protocol#920
aabbccddeeeeee wants to merge 1 commit into
buttplugio:masterfrom
aabbccddeeeeee:agent/support-lelo-f1sv3

Conversation

@aabbccddeeeeee

Copy link
Copy Markdown

Summary

  • Move F1SV3 matching from the F1S V2 protocol to the Lelo Harmony protocol
  • Add an F1SV3 Harmony protocol variant that sends a zero pattern when stopping vibration
  • Add protocol tests for F1SV3 command encoding

Root cause

F1SV3 advertises similarly to the existing F1S V2 entry, but tested hardware exposes Harmony-style control characteristics and does not respond to the F1S V2 motor payloads. The device vibrates with Harmony-style writes to the tx characteristic and requires a different zero-speed stop payload.

Testing

  • cargo test -p buttplug_server lelo_harmony::test
  • cargo test -p buttplug_tests --test test_device_protocols lelo_harmony_protocol
  • cargo test -p buttplug_tests --test test_device_protocols lelo_f1s_v2_protocol
  • Manual test with LELO F1S V3 on macOS/Intiface Central

@CLAassistant

CLAassistant commented Jul 21, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@aabbccddeeeeee
aabbccddeeeeee marked this pull request as ready for review July 21, 2026 23:47
@blackspherefollower

Copy link
Copy Markdown
Collaborator

I wonder if my v3 is of an earlier generation that did support the original control structure too...

@blackspherefollower
blackspherefollower self-requested a review July 22, 2026 10:40
@blackspherefollower blackspherefollower self-assigned this Jul 22, 2026
@aabbccddeeeeee
aabbccddeeeeee force-pushed the agent/support-lelo-f1sv3 branch from a6e3603 to c22ad03 Compare July 22, 2026 23:11
@jayyyyyyyyy

Copy link
Copy Markdown

Hi all,

I am having an issue here as well and claude told me to comment this:
Device: Lelo F1S V3
Firmware: F1SV3 F.REV.M
Intiface version: 3.1.1+43 (Android)
Symptom: Device connects, sensor inputs register, no motor output
Log error: Characteristic rx (00000a04-0000-1000-8000-00805f9b34fb) not found
nRF Connect findings: 0x0A04 does not exist on this device. Device advertises a 0xFFF0 service with 0xFFF1 (READ, WRITE) and 0xFFF2 (NOTIFY, READ, WRITE), consistent with Harmony protocol.

Thanks

@aabbccddeeeeee
aabbccddeeeeee force-pushed the agent/support-lelo-f1sv3 branch from c22ad03 to 81be9bb Compare August 24, 2026 14:20
@aabbccddeeeeee

Copy link
Copy Markdown
Author

This PR has been updated and rebased on current master.

Summary of the current patch:

  • Adds LELO F1S V3 as a separate protocol instead of treating F1SV3 as lelo-f1sv2.
  • Removes the F1SV3 name match from the existing F1S V2 protocol to avoid probing V2-only characteristics.
  • Adds a dedicated lelo-f1sv3 config using the observed Harmony-style BLE layout:
    • service 0xFFF0
    • 0xFFF1 read/write
    • 0xFFF2 notify/read/write
  • Avoids the V2-only rx / whitelist characteristics that are not present on F1S V3 devices.
  • Adds an F1S V3 initializer that performs the observed authorization handshake and then routes output through the Harmony-style command path.
  • Adds protocol tests for F1S V3.
  • Keeps existing F1S V2 behavior unchanged.

Runtime testing on a physical LELO F1S V3 confirmed that vibration output works with this implementation.

The implementation also includes an optional smoothing/filtering path for very rapid vibration changes. This is intended to improve behavior with dense beat/script patterns where the device can otherwise miss short pulses or produce gaps. The
smoothing behavior is configurable and should not silently change behavior for devices/users that do not want it.

Validation performed locally:

  • cargo build -p buttplug_server_device_config
  • cargo test -p buttplug_tests --test test_device_protocols lelo_f1s_v3_protocol

The PR branch was force-pushed to replace the older conflicting commit with a clean single commit based on current master.

@GuillaumeDua GuillaumeDua left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent confirmation: I have an F1S V3 running here (Windows 11, Intel AX210, Intiface Central 3.1.1+43).

Commands land once they go to 0000fff2, so your endpoint conclusion is right.
I got there with a user config putting F1SV3 under lelo-harmony, which is the same two characteristics you reach through generic0 and txvibrate.
test_lelo_f1sv3.yaml matches what I see on the wire.

A few things from reading the diff:

  1. lelo-f1sv3.yml uses 36adf7ce-98bf-4fad-b916-b44d20a5d9e1 for both defaults.id and configurations[0].id. load_user_config resolves base_id with find(|x| x.1.id() == base_id) over a HashMap, so two definitions sharing an id make that lookup order dependent. Keeping the old id on the configuration is the right half to keep, since existing user configs point at it, so the defaults block is the one that needs a new UUID.

  2. The defaults features reuse 90bd67a5-4601-4c49-97bb-0845ab7011ba and 05fc758b-a3fe-4156-b3ae-9cdcb9ae95c6, which are still lelo-f1sv2's. Two protocols now ship the same feature UUIDs.

  3. With smoothing on, a zero speed returns no command and schedules the write 800 ms later, and the task swallows failures with let _ =. An explicit StopDeviceCmd then doesn't stop the device for up to 800 ms, which is more than "smoothing" suggests to whoever turns it on.

  4. vibrate_smoothing_* goes onto the generic ServerDeviceDefinition and the published schema, and only lelo_harmony.rs reads it. As its own PR it stands alone, and what's left here is a config move plus a small handler change, which is a far easier merge.

  5. f1sv3_harmony() is gated on protocol_variant == "f1sv3", and no shipped YAML sets protocol_variant outside vorze-sa.yml, so nothing reaches it unless a user writes the variant by hand. The use_harmony branch in lelof1sv2.rs is fine by the way, txvibrate is fff2 in that map too.

  6. lelo_harmony.rs and lelof1sv2.rs collapse the repo's one-import-per-line style. rustfmt.toml sets imports_layout = "HorizontalVertical", stable cargo fmt ignores it silently, and the CI fmt check is continue-on-error: true so it won't catch it. cargo +nightly fmt should take that churn back out of the diff.

  7. Minor: in maybe_defer_stop, let state = self.state.clone() shadows the MutexGuard of the same name, which stays alive to the end of the function. Fine today because the spawned task sleeps first.

For @qdot rather than for this PR: adding F1SV3 to lelo-harmony.yml with tx: 0000fff2 and whitelist: 00000a11 fixes the device with no Rust at all, since LeloHarmonyInitializer already runs the same handshake on Endpoint::Whitelist, which that map resolves to 00000a11.
That's the version I ran on hardware. It loses the separate protocol name and the smoothing knob, so it's a trade either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants